import math
import os
import os.path

import yaml
from peewee import Database
from playhouse.sqlite_ext import SqliteExtDatabase
from tinydb import TinyDB

from event_bus import EventBus
from exceptions import LocationNotSetException, LocationHasNoProjectException
from models import Project, Source, Sample, db_proxy
from .source import SourceCollect, CategoryCollect, SourceUnit, SampleUnit


class ProjectState:
    _dirs = ["samples", "exports"]
    current: Project
    sources: TinyDB
    samples: TinyDB
    db: Database
    categories: CategoryCollect

    def has_location(self):
        return len(self.current.location) > 0

    def has_valid_location(self):
        return self.has_location() and os.path.exists(self.current.location)

    def __init__(self):
        self.create()

    def create(self):
        self.current = Project()
        self.current.name = "新建项目"
        self.current.version = 2
        if hasattr(self, "db"):
            self.db.close()
            del self.db
        self.db = SqliteExtDatabase(":memory:",
                                    pragmas=(
                                        ("cache_size", -1024 * 32),
                                        ("journal_mode", "wal")
                                    ))
        db_proxy.initialize(self.db)
        self.db.create_tables([Source, Sample])
        self.refresh_categories()
        EventBus().post_topic(event=None, topic="project_changed")

    def load(self, project_path: str):
        if not os.path.exists(os.path.join(project_path, "project.yaml")):
            raise LocationHasNoProjectException()
        with open(os.path.join(project_path, "project.yaml"), "r") as project_file:
            content = yaml.load(project_file, Loader=yaml.SafeLoader)
            self.current = Project.load_from_dict(content)
        if hasattr(self, "db"):
            self.db.close()
            del self.db
        self.db = SqliteExtDatabase(os.path.join(project_path, "store.db"),
                                    pragmas=(
                                        ("cache_size", -1024 * 32),
                                        ("journal_mode", "wal")
        ))
        db_proxy.initialize(self.db)
        self.db.create_tables([Source, Sample])
        if os.path.exists(os.path.join(project_path, "sources.json")):
            EventBus().post_topic(event=True, topic="toggle_upgrade_sources")
            sources = TinyDB(os.path.join(project_path, "sources.json"))
            with db_proxy.atomic():
                for rec in sources:
                    Source.create(path=rec["path"],
                                  name=rec["name"],
                                  fingerprint=rec["fingerprint"])
                sources.close()
            os.remove(os.path.join(project_path, "sources.json"))
            EventBus().post_topic(event=False, topic="toggle_upgrade_sources")
        if os.path.exists(os.path.join(project_path, "samples.json")):
            EventBus().post_topic(event=True, topic="toggle_upgrade_samples")
            samples = TinyDB(os.path.join(project_path, "samples.json"))
            with db_proxy.atomic():
                for rec in samples:
                    Sample.create(source=rec["source"],
                                  source_fingerprint=rec["source_fingerprint"],
                                  target_fingerprint=rec["target_fingerprint"],
                                  crop_x=rec["crop_start"][0],
                                  crop_y=rec["crop_start"][1],
                                  crop_width=rec["crop_width"],
                                  crop_height=rec["crop_height"],
                                  categories=rec["categories"])
                    samples.close()
            os.remove(os.path.join(project_path, "samples.json"))
            EventBus().post_topic(event=False, topic="toggle_upgrade_samples")

        self.refresh_categories()
        EventBus().post_topic(event=dict(switch=0), topic="project_changed")

    def save(self):
        if len(self.current.location) == 0:
            raise LocationNotSetException()
        if not os.path.exists(self.current.location):
            os.makedirs(self.current.location)
        with open(os.path.join(self.current.location, "project.yaml"), 'w') as project_file:
            yaml.dump(self.current.to_dict(), stream=project_file, encoding="utf-8")
        self.db = SqliteExtDatabase(os.path.join(self.current.location, "store.db"),
                                    pragmas=(
                                        ("cache_size", -1024 * 64),
                                        ("journal_mode", "wal")
        ))
        db_proxy.initialize(self.db)
        self.db.create_tables([Source, Sample])
        for folder in self._dirs:
            function_folder = os.path.join(self.current.location, folder)
            if not os.path.exists(function_folder):
                os.makedirs(function_folder)
        self.refresh_categories()
        EventBus().post_topic(event=None, topic="project_changed")

    def refresh_categories(self):
        if hasattr(self, "categories"):
            self.categories.clear()
        else:
            self.categories = CategoryCollect()
        self.categories.append(*self.current.categories)
        for s in Sample.select():
            self.categories.append(*s.categories)
        self.current.categories = {c for c in self.categories}
        with open(os.path.join(self.current.location, "project.yaml"), 'w') as project_file:
            yaml.dump(self.current.to_dict(), stream=project_file, encoding="utf-8")

    @staticmethod
    def load_sources(size: int = 100):
        page_content = SourceCollect()
        request_page = 1
        total_sources = Source.select().count()
        total_pages = math.ceil(total_sources / size)
        while True:
            page_content.clear()
            for source in Source.select().order_by(Source.serial).paginate(request_page, size):
                unit = SourceUnit(filename=source.name, filepath=source.path, fingerprint=source.fingerprint)
                for sample in Sample.select() \
                        .where(Sample.source_fingerprint == source.fingerprint) \
                        .order_by(Sample.serial):
                    unit.append(SampleUnit(fingerprint=sample.target_fingerprint,
                                           crop_start=(sample.crop_x, sample.crop_y),
                                           crop_size=(sample.crop_width, sample.crop_height),
                                           categories=set(sample.categories)))
                page_content.append(unit)
            request_page = yield page_content, request_page, total_pages
            request_page = min([max([1, request_page]), total_pages])
